{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Step 1: Loading a Detection Dataset in FiftyOne"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In our first step, we will be covering how you can load an object detection dataset into FiftyOne. Detection datasets usually come in a standard format that FiftyOne can load in one or two lines for you, making creating datasets fast and easy! However, not all datasets come in a known format and sometimes we have to add the detections on manually ourselves. With just a few more steps, FiftyOne still makes loading custom datasets easy. \n",
    "\n",
    "Let's take a look first at loading a common format."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Loading a Common Format Detection Dataset\n",
    "Detection datasets can come in many forms, but usually stick to a standard. For quick ingestion, FiftyOne is familiar with COCO, VOC, YOLO, KITTI, and FiftyOne formatted datasets. Check out each one to confirm the folder and file setup matches what your structure is. While uncommon, certain datasets tools will rename or move certain files, such as `data.yaml` in a YOLO dataset instead of `dataset.yaml`. \n",
    "\n",
    "Once you have found the correct format of your dataset, we can follow the same pattern for each type: \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install fiftyone"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import fiftyone as fo\n",
    "\n",
    "name = \"my-dataset\"\n",
    "dataset_dir = \"/path/to/detection-dataset\"\n",
    "\n",
    "# Create the dataset\n",
    "dataset = fo.Dataset.from_dir(\n",
    "    dataset_dir=dataset_dir,\n",
    "    dataset_type=fo.types.COCODetectionDataset, # Change with your type\n",
    "    name=name,\n",
    ")\n",
    "\n",
    "# View summary info about the dataset\n",
    "print(dataset)\n",
    "\n",
    "# Print the first few samples in the dataset\n",
    "print(dataset.head())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Check out the docs for each format to find optional parameters you can pass for things like train/test split, subfolders, or labels paths."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Loading a Custom Format Detection Dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sometimes datasets don't come in a common format or maybe you are just adding some additional labels to an existing dataset. Adding detections to these datasets is still easy with FiftyOne. We will learn how to do this by looping over our datasets and adding a new label field to each sample. \n",
    "\n",
    "Let's begin by first loading in a dataset. We will use a [dice detection dataset](https://www.kaggle.com/datasets/nellbyler/d6-dice) from [Kaggle](https://www.kaggle.com/). We start by downloading from `kaggle_hub`, and loading in _just_ the images first. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install kagglehub"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import kagglehub\n",
    "import fiftyone as fo\n",
    "\n",
    "# Download dice dataset\n",
    "path = kagglehub.dataset_download(\"nellbyler/d6-dice\")\n",
    "\n",
    "print(\"Path to dataset files:\", path)\n",
    "\n",
    "images_path = path + \"/d6-dice/Images\"\n",
    "ann_path = path + \"/d6-dice/Annotations\"\n",
    "\n",
    "name = \"Dice Detection\"\n",
    "\n",
    "# Create the FiftyOne dataset\n",
    "dataset = fo.Dataset.from_dir(\n",
    "    dataset_dir=images_path,\n",
    "    dataset_type=fo.types.ImageDirectory,\n",
    "    name=name,\n",
    ")\n",
    "\n",
    "# View summary info about the dataset\n",
    "print(dataset)\n",
    "\n",
    "# Print the first few samples in the dataset\n",
    "print(dataset.head())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see our images have loaded in the app but no annotations yet:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "session = fo.launch_app(dataset)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now the annotations in the dataset are custom, with each image filepath having a corresponding label file in the `Annotations` folder like the following: \n",
    "```\n",
    "Images\n",
    " - IMG_000.jpg\n",
    " - IMG_001.jpg\n",
    " ...\n",
    "Annotations\n",
    " - IMG_000.txt\n",
    " - IMG_001.txt\n",
    " ...\n",
    "```\n",
    "\n",
    "We can loop through our samples, grab the corresponding txt file for our sample, and load in the detections for each one. But first,\n",
    "we need to discuss how a [FiftyOne Detection Label](https://docs.voxel51.com/user_guide/using_datasets.html#object-detection) works! \n",
    "\n",
    "The [Detections](https://docs.voxel51.com/api/fiftyone.core.labels.html#fiftyone.core.labels.Detections) class represents a list of object detections in an image. The detections are stored in the [detections](https://docs.voxel51.com/api/fiftyone.core.labels.html#fiftyone.core.labels.Detections.detections) attribute of the [Detections](https://docs.voxel51.com/api/fiftyone.core.labels.html#fiftyone.core.labels.Detections) object.\n",
    "\n",
    "Each individual object detection is represented by a [Detection](https://docs.voxel51.com/api/fiftyone.core.labels.html#fiftyone.core.labels.Detection) object. The string label of the object should be stored in the [label](https://docs.voxel51.com/api/fiftyone.core.labels.html#fiftyone.core.labels.Detection.label) attribute, and the bounding box for the object should be stored in the [bounding_box](https://docs.voxel51.com/api/fiftyone.core.labels.html#fiftyone.core.labels.Detection.bounding_box) attribute.\n",
    "\n",
    "Lastly, bounding boxes in FiftyOne are always in the following format, normalized to be bounded by [0,1] relative to the image's dimensions:\n",
    "```\n",
    "[<top-left-x>, <top-left-y>, <width>, <height>]\n",
    "```\n",
    "\n",
    "With that explained, let's wrap up by adding detections to our dataset!\n",
    "\n",
    "Our custom dice dataset has a custom annotation format that looks like:\n",
    "```\n",
    "class x_center y_center length width\n",
    "```\n",
    "\n",
    "On top of being in an incorrect bounding box format, the class is off by one for each dice since it starts at 0. So `class` 0 == `side 1` on dice. Let's look at how we can adjust our annotations and add to FiftyOne!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Loop through for each sample in our dataset\n",
    "for sample in dataset:\n",
    "    # Load our annotation file into a list of detections\n",
    "    sample_root = sample.filepath.split(\"/\")[-1].split(\".\")[0]\n",
    "    sample_ann_path = ann_path + \"/\" + sample_root + \".txt\"\n",
    "\n",
    "    with open(sample_ann_path, 'r') as file:\n",
    "        list_of_anns = [line.strip().split() for line in file]\n",
    "\n",
    "    # For each detection, adjust the format and add to our detections list\n",
    "    detections = []\n",
    "\n",
    "    for ann in list_of_anns:\n",
    "\n",
    "        # Make sure to make adjustments to custom formats!\n",
    "        # Move label up one\n",
    "        label = str(int(ann[0]) + 1)\n",
    "\n",
    "        # Adjust bounding box from x_center, y_center, length, width to top_left_x, top_left_y, width, height\n",
    "        bbox = [float(x) for x in ann[1:]] # x,y,l,w\n",
    "        bbox_adjusted = [bbox[0]-bbox[3]/2, bbox[1]-bbox[2]/2, bbox[3], bbox[2]] # x,y,w,h\n",
    "\n",
    "        # Add the object to the sample\n",
    "        det = fo.Detection(\n",
    "            label=label, bounding_box=bbox_adjusted\n",
    "        )\n",
    "\n",
    "        detections.append(det)\n",
    "    \n",
    "    sample[\"ground_truth\"] = fo.Detections(detections=detections)\n",
    "    sample.save()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once it is all done, we can view our dataset to confirm that we were able to load our custom detections!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "session.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![dice_dataset](https://cdn.voxel51.com/dice_dataset.webp)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "Great work! You've learned how to load detection datasets into FiftyOne using both standard formats (COCO, VOC, YOLO) and custom formats by manually adding `fo.Detection` objects.\n",
    "\n",
    "Next up: **Step 2 - Visualizing and Analyzing Detection Data**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "env",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.23"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
